﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;

namespace OmniKits
{
    using Utilities;

    partial class Helpers
    {
        public sealed class SecureBufferHelper { }
        public static SecureBufferHelper SecureBuffer { get; }

#if !PCL
        public static char[] SecureToCharArray(this SecureBufferHelper _, SecureString source)
            => source.SecureToCharArray();
#endif

        #region secure encoding methods

        public static int SecureGetLength<T>(this SecureBufferHelper _, T source, int start, int? count, Action<T, int, int?, TextWriter> encodeAction)
            => source.SecureGetLength(start, count, encodeAction);
        public static void SecureEncode<T>(this SecureBufferHelper _, T source, int start, int? count, IList<char> dest, int destStart, Action<T, int, int?, TextWriter> encodeAction)
            => source.SecureEncode(start, count, dest, destStart, encodeAction);
        public static char[] SecureEncode<T>(this SecureBufferHelper _, T source, int start, int? count, Action<T, int, int?, TextWriter> encodeAction)
            => source.SecureEncode(start, count, encodeAction);

        public static long SecureGetLength<T>(this SecureBufferHelper _, T source, int start, int? count, Action<T, int, int?, Stream> encodeAction)
            => source.SecureGetLength(start, count, encodeAction);
        public static void SecureEncode<T>(this SecureBufferHelper _, T source, int start, int? count, byte[] dest, int destStart, Action<T, int, int?, Stream> encodeAction)
            => source.SecureEncode(start, count, dest, destStart, encodeAction);
        public static byte[] SecureEncode<T>(this SecureBufferHelper _, T source, int start, int? count, Action<T, int, int?, Stream> encodeAction)
            => source.SecureEncode(start, count, encodeAction);

        #endregion

        #region SecureZero*Buffer methods

        public static void SecureZeroByteBuffer(this SecureBufferHelper _, IList<byte> source)
            => source.SecureZeroByteBuffer();
        public static void SecureZeroInt16Buffer(this SecureBufferHelper _, IList<short> source)
            => source.SecureZeroInt16Buffer();
        public static void SecureZeroInt32Buffer(this SecureBufferHelper _, IList<int> source)
            => source.SecureZeroInt32Buffer();
        public static void SecureZeroInt64Buffer(this SecureBufferHelper _, IList<long> source)
            => source.SecureZeroInt64Buffer();
        public static void SecureZeroCharBuffer(this SecureBufferHelper _, IList<char> source)
            => source.SecureZeroCharBuffer();
#if IncludeNonClsCompliantElements
        [CLSCompliant(false)]
        public static void SecureZeroSByteBuffer(this SecureBufferHelper _, IList<sbyte> source)
            => source.SecureZeroSByteBuffer();
        [CLSCompliant(false)]
        public static void SecureZeroUInt16Buffer(this SecureBufferHelper _, IList<ushort> source)
            => source.SecureZeroUInt16Buffer();
        [CLSCompliant(false)]
        public static void SecureZeroUInt32Buffer(this SecureBufferHelper _, IList<uint> source)
            => source.SecureZeroUInt32Buffer();
        [CLSCompliant(false)]
        public static void SecureZeroUInt64Buffer(this SecureBufferHelper _, IList<ulong> source)
            => source.SecureZeroUInt64Buffer();
#endif

        #endregion
    }
}

namespace OmniKits.Utilities
{
    using NullGuard;

    using Core;
    using Extensions;

    public static class SecureBufferUtility
    {
#pragma warning disable 1591

#if !PCL
        public static char[] SecureToCharArray(this SecureString source)
        {
            var chars = new char[source.Length];
            var bstr = Marshal.SecureStringToBSTR(source);
            try
            {
                Marshal.Copy(bstr, chars, 0, source.Length);
            }
            finally
            {
                Marshal.ZeroFreeBSTR(bstr);
            }
            return chars;
        }
#endif

        #region secure encoding methods

        #region to char[]

        public static int SecureGetLength<T>([AllowNull]this T source, int start, int? count, Action<T, int, int?, TextWriter> encodeAction)
        {
            using (var sfw = new SecureFakeWriter())
            {
                encodeAction(source, start, count, sfw);
                return sfw.Count;
            }
        }

        public static void SecureEncode<T>([AllowNull]this T source, int start, int? count, IList<char> dest, int destStart, Action<T, int, int?, TextWriter> encodeAction)
        {
            using (var sbw = new SecureBufferWriter(dest) { Position = destStart })
            {
                encodeAction(source, start, count, sbw);
            }
        }

        public static char[] SecureEncode<T>([AllowNull]this T source, int start, int? count, Action<T, int, int?, TextWriter> encodeAction)
        {
            var buffer = new char[source.SecureGetLength(start, count, encodeAction)];
            try
            {
                source.SecureEncode(start, count, buffer, 0, encodeAction);
            }
            catch
            {
                buffer.SecureZeroCharBuffer();
                throw;
            }
            return buffer;
        }

        #endregion

        #region to byte[]

        public static long SecureGetLength<T>([AllowNull]this T source, int start, int? count, Action<T, int, int?, Stream> encodeAction)
        {
            using (var sfw = new SecureFakeStream())
            {
                encodeAction(source, start, count, sfw);
                return sfw.Length;
            }
        }

        public static void SecureEncode<T>([AllowNull]this T source, int start, int? count, byte[] dest, int destStart, Action<T, int, int?, Stream> encodeAction)
        {
            using (var sbw = new MemoryStream(dest) { Position = destStart })
            {
                encodeAction(source, start, count, sbw);
            }
        }

        public static byte[] SecureEncode<T>([AllowNull]this T source, int start, int? count, Action<T, int, int?, Stream> encodeAction)
        {
            var buffer = new byte[source.SecureGetLength(start, count, encodeAction)];
            try
            {
                source.SecureEncode(start, count, buffer, 0, encodeAction);
            }
            catch
            {
                buffer.SecureZeroByteBuffer();
                throw;
            }
            return buffer;
        }

        #endregion

        #endregion

        #region SecureZero*Buffer methods

        public static void SecureZeroByteBuffer(this IList<byte> source)
        {
            source.ClearArrayData();
        }

        public static void SecureZeroInt16Buffer(this IList<short> source)
        {
            source.ClearArrayData();
        }

        public static void SecureZeroInt32Buffer(this IList<int> source)
        {
            source.ClearArrayData();
        }

        public static void SecureZeroInt64Buffer(this IList<long> source)
        {
            source.ClearArrayData();
        }

        public static void SecureZeroCharBuffer(this IList<char> source)
        {
            source.ClearArrayData();
        }

#if IncludeNonClsCompliantElements
        [CLSCompliant(false)]
        public static void SecureZeroSByteBuffer(this IList<sbyte> source)
        {
            source.ClearArrayData();
        }

        [CLSCompliant(false)]
        public static void SecureZeroUInt16Buffer(this IList<ushort> source)
        {
            source.ClearArrayData();
        }

        [CLSCompliant(false)]
        public static void SecureZeroUInt32Buffer(this IList<uint> source)
        {
            source.ClearArrayData();
        }

        [CLSCompliant(false)]
        public static void SecureZeroUInt64Buffer(this IList<ulong> source)
        {
            source.ClearArrayData();
        }
#endif

        #endregion
    }
}
